﻿using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System;
using System.Data.Entity;

namespace TraceThisItem.Domain
{
	[DataObject]
	public sealed class Facade
	{
		#region Variables

		private static Facade instance;
		private static object SyncRoot = new object();
		private PersistenceManager manager;

		#endregion Variables

		#region Constructors

		private Facade()
		{
			manager = new PersistenceManager();
		}

		#endregion Constructors

		#region Singleton

		public static Facade Instance
		{
			get
			{
				if (instance == null)
				{
					lock (SyncRoot)
					{
						if (instance == null)
						{
							instance = new Facade();
						}
					}
				}

				return instance;
			}
		}

		#endregion Singleton

		#region Project
		public string GetProjectNameById(int projectId)
		{
			var query = from p in manager.Projects
						where (p.Id == projectId)
						select p;

			return query.Single().Name;

		}


		[DataObjectMethod(DataObjectMethodType.Insert)]
		public void AddProject(Project project)
		{
			manager.Projects.Add(project);
			manager.SaveChanges();
		}

		[DataObjectMethod(DataObjectMethodType.Select)]
		public IEnumerable<Project> GetProjects()
		{
			var query = from p in manager.Projects
						select p;

			return query.AsEnumerable();
		}

		[DataObjectMethod(DataObjectMethodType.Select)]
		public IEnumerable<Project> GetProjectsOwnedByUser(string userName)
		{
			var query = from p in manager.Projects
						where (p.Owner.UserName == userName)
						select p;

			return query.AsEnumerable();
		}

		public IEnumerable<Project> GetProjectsByUser(string userName)
		{
			var query = from p in manager.Projects
						where (p.Owner.UserName == userName) 
						select p;

			List<Project> filteredProjects = query.ToList<Project>();
			 
			foreach (Team team in this.GetTeamsContainingUser(userName).ToList<Team>())
			{
				if (!filteredProjects.Contains(team.Project))
				{
					filteredProjects.Add(team.Project);
				}				
			}

			
			return filteredProjects.AsEnumerable();
		}

		


		

		[DataObjectMethod(DataObjectMethodType.Delete)]
		public void RemoveProject(Project project)
		{
			var projectWithId = manager.Projects.Find(project.Id);
			manager.Projects.Remove(projectWithId);
			manager.SaveChanges();
		}

		[DataObjectMethod(DataObjectMethodType.Update)]
		public void UpdateProject(Project project)
		{
			var p = manager.Projects.Find(project.Id);
			manager.Entry(p).CurrentValues.SetValues(project);
			manager.SaveChanges();
		}

		[DataObjectMethod(DataObjectMethodType.Select)]
		public IEnumerable<Project> GetProjectsByUser(User user)
		{
			var query = from p in manager.Projects
						where p.Owner.Equals(user)
						select p;

			return query.AsEnumerable();
		}

		public Project GetProjectById(int projectId)
		{
			var query = from p in manager.Projects
						where (p.Id == projectId)
						select p;

			return query.Single();

		}

		#endregion Project

		#region Team

		[DataObjectMethod(DataObjectMethodType.Insert)]
		public void AddTeam(Team team)
		{
			manager.Teams.Add(team);
			manager.SaveChanges();
		}

		[DataObjectMethod(DataObjectMethodType.Select)]
		public Team GetTeamById(int id)
		{
			return manager.Teams.Find(id);
		}

		public IEnumerable<Team> GetTeamsContainingUser(string userName)
		{
			var user = manager.Users.Find(userName);
			return user.Teams.AsEnumerable();
		}

		
		[DataObjectMethod(DataObjectMethodType.Select)]
		public IEnumerable<Team> GetTeams()
		{
			var query = from p in manager.Teams.Include(t => t.Project)
						select p;

			return query.ToList();
		}

		[DataObjectMethod(DataObjectMethodType.Select)]
		public IEnumerable<Team> GetTeamsByProjectId(int projectId)
		{
			var project = manager.Projects.Find(projectId);

			return project.Teams;
		}

		[DataObjectMethod(DataObjectMethodType.Delete)]
		public void RemoveTeam(Team team)
		{
			var teamWithId = manager.Teams.Find(team.Id);
			manager.Teams.Remove(teamWithId);
			manager.SaveChanges();
		}

		[DataObjectMethod(DataObjectMethodType.Select)]
		public IEnumerable<User> GetUsersByTeam(int teamId)
		{
			var team = manager.Teams.Find(teamId);

			if (team != null)
			{
				return team.Users;
			}

			return null;
		}

		[DataObjectMethod(DataObjectMethodType.Insert)]
		public void AddUserToTeam(int teamId, string username)
		{
			var team = manager.Teams.Find(teamId);
			var user = manager.Users.Find(username);

			team.AddUser(user);
			manager.SaveChanges();
		}

		#endregion Team Handlers
		
		#region Item Types Handlers
        [DataObjectMethod(DataObjectMethodType.Select)]
        public IEnumerable<ItemType> GetItemTypesFromProject(int projectId)
        {
            var query = from it in manager.ItemTypes.Include("Workflow")
                        where (it.ProjectId == projectId)
							&& (it.Workflow.Nodes.Count > 0)
                        select it;
			
			return query.AsEnumerable();
        }

		public ItemType GetItemTypeById(int itemTypeId)
		{
			return manager.ItemTypes.Find(itemTypeId);
		}

				
		[DataObjectMethod(DataObjectMethodType.Delete)]
		public void RemoveItemType(ItemType itemType)
		{
			var itemTypeWithId = manager.ItemTypes.Find(itemType.Id);
			manager.ItemTypes.Remove(itemTypeWithId);
			manager.SaveChanges();
		}

		[DataObjectMethod(DataObjectMethodType.Update)]
		public void UpdateItemType(ItemType itemType)
		{
			var itemTypeWithId = manager.ItemTypes.Find(itemType.Id);
			itemTypeWithId.Name = itemType.Name;		
			manager.SaveChanges();
		}

		public void AddItemType(ItemType newItemType)
		{
			var project = this.GetProjectById(newItemType.ProjectId);
			var workflow = this.GetWorkFlowById(newItemType.WorkflowId);

			newItemType.Project = project;
			newItemType.Workflow = workflow;

			manager.ItemTypes.Add(newItemType);
			manager.SaveChanges();
		}

		

		

		#endregion
		
		#region Workflow

		[DataObjectMethod(DataObjectMethodType.Insert)]
		public void AddWorkFlow(Workflow item)
		{
			manager.Workflows.Add(item);
			manager.SaveChanges();
		}

		[DataObjectMethod(DataObjectMethodType.Select)]
		public IEnumerable<Workflow> GetWorkFlows()
		{
			var query = from wf in manager.Workflows						
						select wf;

			return query.AsEnumerable();
		}

		[DataObjectMethod(DataObjectMethodType.Select)]
		public Workflow GetWorkFlowById(int workflowId)
		{
			var query = from wf in manager.Workflows
						where wf.Id == workflowId
						select wf;

			return query.SingleOrDefault();

		}

		[DataObjectMethod(DataObjectMethodType.Delete)]
		public void RemoveWorkflow(Workflow item)
		{
			var workflowToDelete = manager.Workflows.Find(item.Id);
			manager.Workflows.Remove(workflowToDelete);
			manager.SaveChanges();
		}

		#endregion Workflow

		#region User

		[DataObjectMethod(DataObjectMethodType.Insert)]
		public void AddUser(User user)
		{
			manager.Users.Add(user);
			manager.SaveChanges();
		}

		[DataObjectMethod(DataObjectMethodType.Select)]
		public User GetUser(string username)
		{
			return manager.Users.Find(username);
		}

		[DataObjectMethod(DataObjectMethodType.Select)]
		public IEnumerable<User> GetUsers()
		{
			return manager.Users;
		}

		[DataObjectMethod(DataObjectMethodType.Delete)]
		public void RemoveUser(User user)
		{
			manager.Entry(user).State = System.Data.EntityState.Deleted;
			manager.SaveChanges();
		}

		[DataObjectMethod(DataObjectMethodType.Delete)]
		public void RemoveUserFromTeam(string username, int teamId)
		{
			var user = manager.Users.Find(username);
			var team = user.Teams.Single(t => t.Id == teamId);

			user.Teams.Remove(team);

			manager.SaveChanges();
		}

		[DataObjectMethod(DataObjectMethodType.Select)]
		public IEnumerable<User> GetUsersByProject(int projectId)
		{
			var project = manager.Projects.Find(projectId);
			List<User> responsibles = new List<User>();

			if (project != null)
			{
				foreach (Team team in project.Teams)
				{
					responsibles.InsertRange(responsibles.Count, team.Users);
					if (!responsibles.Contains(project.Owner))
					{
						responsibles.Add(project.Owner);
					}
				}

				return responsibles;
			}

			return null;
		}

		[DataObjectMethod(DataObjectMethodType.Select)]
		public IEnumerable<User> GetUsersExceptFromTeam(int teamId)
		{
			var query = from u in manager.Users
						where !u.Teams.Any(ut => ut.Id == teamId)
						select u;

			return query.AsEnumerable();
		}

		#endregion User


		[DataObjectMethod(DataObjectMethodType.Select)]
		public Item GetItemById(int itemId)
		{
			return manager.Items.Find(itemId);
		}
		
		

        [DataObjectMethod(DataObjectMethodType.Select)]
        public IEnumerable<Item> GetItemsByProject(int projectId)
        {
            var query = from i in manager.Items
                        where (i.Project.Id == projectId)
                        select i;

            return query.AsEnumerable();
		}


		#region Item Handlers
		[DataObjectMethod(DataObjectMethodType.Insert)]
		public void AddItem(Item newItem)
		{
			manager.Items.Add(newItem);
			manager.SaveChanges();
		}

		[DataObjectMethod(DataObjectMethodType.Select)]
		public IEnumerable<ItemStatus> GetTracebilityByItemId(int itemId)
		{
			return manager.Items.Find(itemId).Traceability;
		}
		[DataObjectMethod(DataObjectMethodType.Select)]
		public IEnumerable<StatusNode> GetNextStatusesByWorkflowIdAndCurrentStatusId(int workflowId,int currentStatusId)
		{
			var currentWf = manager.Workflows.Find(workflowId);

			foreach (StatusNode sn in currentWf.Nodes)
			{
				if (sn.Id == currentStatusId)
					return sn.NextStatusNodes;
			}

			return null;
		}
		[DataObjectMethod(DataObjectMethodType.Update)]
		public void UpdateItem(int itemId, string comments, User responsible, byte priority, StatusNode nextStatus)
		{
			var thisItem = manager.Items.Find(itemId);
			thisItem.Comments = comments;
			thisItem.Priority = priority;

			var newStatus = new ItemStatus();
			newStatus.Date = System.DateTime.Now;
			newStatus.Responsible = responsible;
			newStatus.StatusNode = nextStatus;
			manager.ItemStatus.Add(newStatus);

			thisItem.Traceability.Add(newStatus);

			manager.SaveChanges();
		}
		#endregion Item Handlers

		[DataObjectMethod(DataObjectMethodType.Select)]
		public StatusNode GetStatusNodeById(int statusNodeId)
		{
			return manager.StatusNodes.Find(statusNodeId);
		}

		[DataObjectMethod(DataObjectMethodType.Select)]
		public IEnumerable<StatusNode> GetStatusNodesByWorkFlow(int workflowId)
		{
			return manager.StatusNodes.Where(n => n.WorkflowId == workflowId);			
		}

		[DataObjectMethod(DataObjectMethodType.Select)]
		public IEnumerable<StatusNode> GetNextPossibleStatusNodesByWorkFlowAndCurrentStatusNode(int workflowId, int currentStatusNodeId)
		{
			return manager.StatusNodes.Where(n => n.WorkflowId == workflowId && n.Id != currentStatusNodeId);
		}


		public void Save()
		{
			this.manager.SaveChanges();
		}

		[DataObjectMethod(DataObjectMethodType.Update)]
		public void UpdateStatusNode(StatusNode statusNode)
		{
			var node = manager.StatusNodes.Find(statusNode.Id);
			node.Name = statusNode.Name;
			Save();
		}

		public void AddNextStatusNode(int currentId, int nextId)
		{
			var current = manager.StatusNodes.Find(currentId);
			var next = manager.StatusNodes.Find(nextId);

			if (!current.NextStatusNodes.Contains(next))
			{
				current.NextStatusNodes.Add(next);
				Save();
			}
		}

		public void RemoveNextStatusNode(int currentId, int nextId)
		{
			var current = manager.StatusNodes.Find(currentId);
			var next = manager.StatusNodes.Find(nextId);

			if (current.NextStatusNodes.Contains(next))
			{
				current.NextStatusNodes.Remove(next);
				Save();
			}
		}

		public void RemoveStatusNode(int idToDelete)
		{
			var current = manager.StatusNodes.Find(idToDelete);
			manager.StatusNodes.Remove(current);
			Save();
		}
	}
}